Funções
Arquivos
In [1]:
def dobra(x):
return x * 2
In [2]:
dobra(10)
Out[2]:
Vale notar que o Python não faz checagem de tipos, então podemos usar nossa função dobra()
com outros tipos de argumentos:
In [9]:
dobra('do')
Out[9]:
In [10]:
dobra([1, 2, 3])
Out[10]:
Uma função pode receber mais de um parâmetro:
In [16]:
def soma(a, b, c, d):
return a + b + c + d
In [17]:
soma(1, 2, 3, 4)
Out[17]:
In [19]:
soma('h', 'o', 'j', 'e')
Out[19]:
A documentação de funções é feita utilizando docstring
. Docstring são lorem ipsum dolor sit amet:
In [22]:
def fatorial(n):
""" Retorna o fatorial de n (n!)"""
return 1 if n < 1 else n * fatorial(n - 1)
In [25]:
fatorial(3), fatorial(4), fatorial(5)
Out[25]:
In [26]:
fat = fatorial
fat(3), fat(4), fat(5)
Out[26]:
In [27]:
fat
Out[27]:
In [28]:
type(fat)
Out[28]:
É possível acessar atributos desse objeto function:
In [29]:
fat.__doc__
Out[29]:
Para conhecermos os atributos e métodos de um objeto function
podemos usar a função dir()
que retorna os métodos atributos de um objeto
In [30]:
dir(fat)
Out[30]:
Os métodos e atributos envoltos em __ são conhecidos como Métodos Mágicos ou Métodos Dunder (Double UNDERline) e serão vistos no minicurso de Orientação a Objetos
In [31]:
fat.__name__
Out[31]:
In [32]:
fat.__doc__
Out[32]:
É possível acessar o metadados e o bytecode dessas funções:
In [33]:
fat.__code__
Out[33]:
In [34]:
dir(fat.__code__)
Out[34]:
In [38]:
fat.__code__.co_name
Out[38]:
In [40]:
fat.__code__.co_varnames
Out[40]:
Bytecode:
In [41]:
fat.__code__.co_code
Out[41]:
In [42]:
import dis
dis.dis(fat)
Como mostramos anteriormente é possível enviar funções como argumentos de outras funções. No caso usamos esse artifício para mudar o funcionamento padrão da função de ordenação sorted()
:
In [53]:
bromas = {'z': 10, 'n': 5, 'm': 7}
bromas
Out[53]:
In [54]:
sorted(bromas.items())
Out[54]:
In [57]:
def pega_segundo(sequencia):
return sequencia[1]
sorted(bromas.items(), key=pega_segundo)
Out[57]:
Neste exemplo definimos a funçao pega_segundo e enviamos ela como argumento para a função sorted()
O Python permite a atribuição de valores padrão para argumentos de uma função. Ao chamar essa função esses argumentos são opcionais, sendo utilizado o valor padrão fornecido na definição da função.
Por exemplo vamos criar uma função que converte um valor em dólar para real com o preço do dólar como argumento com valor padrão:
In [9]:
def dolar_para_real(valor_real, dolar=3.53):
return valor_real * dolar
Para calular um preço de um produto de, por exemplo, U$89,00 só precisamos passar esse valor:
In [12]:
dolar_para_real(89)
Out[12]:
Supondo que queiramos calcular o preço do produto no ano passado quando o valor do dólar estava menor:
In [13]:
dolar_para_real(89, 2.8)
Out[13]:
Muitas funções da biblioteca padrão usam argumentos padrão para simplificar e extender seus usos. Muita funções que vimos neste curso fazem isso, como, por exemplo a função str.split()
.
Para mostrar isso vamos recorrer a sua documentação que é invocada ao passar essa função como argumento para a função help()
:
In [15]:
help(str.split)
Como visto a função split possui dois argumentos com valores padrão: separador e número máximo de splits. Por padrão o separador é um espaço em branco e o número máximo de splits é todos os possíveis, como podemos observar neste exemplo:
In [16]:
'Frase sem sentido algum para ser usada como exemplo'.split()
Out[16]:
Podemos mudar esse comportamento passando outros argumentos:
In [21]:
frase = 'Frase sem sentido algum para ser usada como exemplo'
frase.split(' ', 1) # somente 1 split foi feito gerando uma lista de dois elementos
Out[21]:
In [22]:
url = 'www.dominio.com.br'
url.split('.')
Out[22]:
In [24]:
url.split('.', 1) # para separar somente o www do resto
Out[24]:
Outra função que também faz isso é a função open()
usada para abrir arquivos:
In [32]:
arq = open('arq.txt', 'w') # passa nome do arquivo e modo abertura 'w' (escrita)
arq # arquivo aberto
Out[32]:
In [33]:
arq.close() # fechando arquivo
In [34]:
arq = open('arq.txt') # por padrão o modo de abertura é 'r' (leitura)
arq
Out[34]:
In [35]:
arq.close()
Veremos mais sobre esta função ainda nesta aula.
Cuidado com argumentos padrões!
Os argumentos padrões de funções são executados apenas uma vez e isso pode causar alguns comportamentos "estranhos".
Suponhamos que queremos criar uma função anexa()
que adiciona um elemento a uma lista e, se a lista não for passada, criamos uma nova:
In [36]:
def anexa(elemento, lista=[]):
lista.append(elemento)
return lista
In [37]:
anexa(1)
Out[37]:
In [38]:
anexa(2)
Out[38]:
In [39]:
anexa(3)
Out[39]:
Como dito anteriormente o valor do argumento da lista []
(que cria uma lista) é executado apenas uma vez, portanto a mesma lista é usada sempre que chamamos a função anexa()
. Para criarmos uma anova lista quando não nos é passado uma fazemos:
In [41]:
def anexa(elemento, lista=None):
if not lista:
lista = []
lista.append(elemento)
return lista
Desse jeito criamos uma nova lista cada vez que a função é executada:
In [44]:
lista = anexa(10)
lista
Out[44]:
In [45]:
anexa(5)
Out[45]:
In [46]:
anexa(20, lista)
lista
Out[46]:
Como já vimos anteriormente (porém não foi explicado como) o Python permite que os argumentos da função sejam chamados por seu nome e não somente por sua posição:
In [42]:
anexa(elemento=100, lista=[1, 2, 3])
Out[42]:
In [47]:
'Exemplo de split chamado pelo nome dos argumentos'.split(sep=' ', maxsplit=-1)
Out[47]:
Uma função da biblioteca padrão do Python que faz uso extensivo de argumentos padrões é a timedelta()
da biblioteca datetime (que trabalha com datas e horários). Essa função é usada para representar durações, diferenças entre datas ou horários:
In [4]:
from datetime import date, timedelta
hoje = date.today()
hoje # objeto do tipo date
Out[4]:
In [78]:
hoje.year, hoje.month, hoje.day # atributos de date: day, month e year
Out[78]:
foo
In [69]:
hoje + timedelta(days=1) # amanhã
Out[69]:
In [72]:
hoje - timedelta(days=1) # ontem
Out[72]:
In [70]:
hoje + timedelta(days=2) # depois de amanhã
Out[70]:
In [73]:
hoje - timedelta(days=2) # antes de ontem
Out[73]:
In [71]:
hoje + timedelta(days=7) # semana que vem
Out[71]:
In [86]:
hoje
Out[86]:
In [82]:
hoje + timedelta(days=30) # mês que vem
Out[82]:
Como nem todo mês possui 30 dias pode ser necessário saber qual o próximo mês. Para isso é melhor usar a função datetime.replace()
que retorna a mesma data com os valores fornecidos trocados:
In [89]:
hoje.replace(month=hoje.month + 1) # mesmo dia e ano no próximo mês
Out[89]:
In [90]:
hoje.replace(year=hoje.year + 1) # mesmo dia e mês no próximo ano
Out[90]:
timedelta()
também pode ser usado com datetimes (data e hora):
In [3]:
from datetime import datetime
agora = datetime.now()
agora
Out[3]:
In [77]:
agora.year, agora.month, agora.day, agora.hour, agora.minute, agora.second, agora.microsecond
Out[77]:
In [79]:
agora + timedelta(hours=1) # daqui uma hora
Out[79]:
In [80]:
agora - timedelta(hours=1) # uma hora atrás
Out[80]:
In [81]:
agora + timedelta(hours=2, minutes=30) # daqui 2 horas e meia
Out[81]:
Subtrair dates e datetimes gera objetos timedelta que representam a diferença de tempo entre os dois:
In [11]:
daqui_a_pouco = agora + timedelta(minutes=15, seconds=45)
daqui_a_pouco - agora
Out[11]:
In [9]:
Out[9]:
Chamar funções dando nomes aos seus argumentos, em conjunto com bons nomes de funções e argumentos, é uma ótima forma de aumentar a legibilidade de seu código.
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [ ]:
In [1]:
max(1, 2) # funciona com 2 argumentos
Out[1]:
In [14]:
max(1, 2, 3) # 3 argumentos
Out[14]:
In [17]:
max(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18) # muitos argumentos
Out[17]:
O que acontece é que esses vários argumentos são empacotados para uma tupla e então a partir dessa tupla de elementos é possível encontrar o máximo. A funcão sum()
que soma os elementos de uma sequência não suporta argumentos abritrários, vamos fazer uma versão dessa função que suporte isso:
In [19]:
sum(1, 2, 3, 4) # não suporta
O empacotamento de argumentos é feito na definição dos argumentos da função usando o operador *
, conforme é mostrado a seguir. Vamos começar analisando o resultado desse argumento para depois implementar a funcionalidade de soma:
In [37]:
def soma(*numeros):
print('O tipo é: {}'.format(type(numeros)))
print('Valores: {}'.format(numeros))
Só para não haver dúvidas: *numeros
não é um ponteiro. O que realmente acontece é que os valores recebidos ao invocar a função soma()
serão empacotados para o argumento numeros
.
In [31]:
soma(-1, 0, 1)
Nesse exemplo a função soma()
foi chamada com os valores -1, 0, 1 que foram empacotados na tupla numeros. Sabendo disso podemos calcular a soma desses elementos:
In [32]:
def soma(*numeros):
soma = 0
for num in numeros:
soma += num
return soma
In [33]:
soma(1, 2, 3, 4)
Out[33]:
In [35]:
soma(-2, 0, 3)
Out[35]:
É possível criar função que receba alguns argumentos fixos e argumentos arbitrários:
In [41]:
def foo(bar, *baz):
print(type(bar), type(baz))
print('bar: {}, baz: {}'.format(bar, baz))
In [42]:
foo(1, 2, 3, 4, 5)
In [43]:
foo([1, 2, 3], 10, 'aba', False, (1, 2, 3))
Do mesmo jeito que empacotamos argumentos recebidos na chamada de uma função podemos desempacotar argumentos para enviar as funções:
In [47]:
numeros = -1, 0, 10
max(*numeros)
Out[47]:
No exemplo anterior desempacotamos a tupla com os valores -1, 0, 10 e, ao invés de enviar uma tupla, mandamos cada um deles como um argumento separado. Para deixar esse conceito claro criaremos uma função que recebe argumentos desempacotados:
In [48]:
def soma(a, b, c):
""" Soma três números a, b, e c (a + b + c) """
return a + b + c
Normalmente faríamos:
In [50]:
soma(1, 2, 3)
Out[50]:
Porém podemos desempacotar uma sequência de 3 elementos e enviá-los para a função:
In [51]:
números = -1, 0, 1
soma(*números)
Out[51]:
Se a lista for maior ou menor uma exceção será levantada:
In [53]:
números = -1, 0
soma(*números)
In [54]:
números = -1, 0, 1, 10
soma(*números)
Para ficar mais claro ainda vamos criar uma função que deve receber, obrigatoriamente, três argumentos fixos e, opcionalmente, quantos mais valores forem enviados:
In [63]:
def foo(a, b, c, *args):
print('a: {} {}'.format(a, type(a)))
print('b: {} {}'.format(b, type(b)))
print('c: {} {}'.format(c, type(c)))
print('*args: {} {}'.format(args, type(args)))
In [64]:
args = ['foobarbaz', False, 10]
foo(*args)
Ao receber argumentos empacotados, como feito na função foo(a, b, c, *args)
, permitimos o envio de uma quantidade arbitrária de argumentos. Isso inclui o envio de nenhum argumento, por esse motivos as funções embutidas min()
e max()
definem dois argumentos fixos e depois recebem mais argumentos empacotados:
In [69]:
min(10) # levanta exceção
In [72]:
min(10, -10) # correto
Out[72]:
In [73]:
min(10, -10, 0) # também correto
Out[73]:
O mesmo vale para nossa função foo()
:
In [74]:
foo(1)
In [75]:
foo(1, 2)
In [76]:
foo(1, 2, 3)
In [77]:
foo(1, 2, 3, 4, 5, 6, 7, 8, 9)
Empacotamento e desempacotamento de argumentos são conceitos importantes, uma vez que são usados extensivamente em bibliotecas e frameworks Python. Funções embutidas como format()
, max()
e min()
usam. Além disso, muitos métodos das Class-Based Views no framework web Django também usam.
Além de empacotar e desempacotar argumentos em/de sequências também é possível fazer isso para dicionários:
In [60]:
def foo(a, b, c):
print('a: {} {}'.format(a, type(a)))
print('b: {} {}'.format(b, type(b)))
print('c: {} {}'.format(c, type(c)))
In [81]:
kwargs = {'a': 1.5, 'b': True, 'c': 'alo'}
foo(**kwargs) # desempacotando dicionário kwargs para função foo()
O que acontece por trás disso é: os argumentos com o nome das chaves do dicionário recebem o respectivo valor, portanto é necessário se atentar com as chaves e nomes do argumentos:
In [82]:
kwargs = {'q': 10, 'x': 'foo', 'a': 123}
foo(**kwargs)
Assim como visto anteriormente também é possível, ao criar uma função, empacotar os argumentos em dicionários:
In [83]:
def foo(a, b, c, **kwargs): # kwargs = KeyWord Arguments (argumentos de palavra-chave)
print('a: {} {}'.format(a, type(a)))
print('b: {} {}'.format(b, type(b)))
print('c: {} {}'.format(c, type(c)))
print('kwargs: {} {}'.format(kwargs, type(kwargs)))
In [84]:
foo(1, 2, 3)
In [85]:
foo(1, 2, 3, nome='José', idade=100, vivo=True)
A função str.format()
recebe argumentos posicionais e de palavra-chave arbitrários que devem corresponder a quantidade de variáveis a ser substituidas na string de formatação:
In [86]:
'{0}'.format(1)
Out[86]:
In [87]:
'{0} {1}'.format(1, 2)
Out[87]:
Podemos desempacotar uma sequência e enviar à função de formatação:
In [88]:
numeros = [1, 2, 3, 4, 5]
'{0} {1} {2} {3} {4}'.format(*numeros)
Out[88]:
In [90]:
'{nome} é {sexo} e tem {idade} anos de idade.'.format(nome='Joana', sexo='mulher', idade=35)
Out[90]:
Podemos desempacotar um dicionários e enviar essas informações à função:
In [92]:
dados = {'nome': 'Joana', 'sexo': 'mulher', 'idade': 35}
'{nome} é {sexo} e tem {idade} anos de idade.'.format(**dados)
Out[92]:
Para criar uma função que receba argumentos arbitrários é preciso criar uma função que empacote tanto os argumentos posicionais (em uma tupla) quanto os nomeados (em um dicionários):
In [94]:
def silverbullet(*args, **kwargs):
print('args: {} {}'.format(args, type(args)))
print('kwargs: {} {}'.format(kwargs, type(kwargs)))
In [95]:
silverbullet(1, 2, 3, 4, a=10, b=20, c=30)
In [97]:
foo = 1, 2, 3
bar = {'abc': False, 'def': 'alololo'}
silverbullet(-1, -10, *foo, a=150, b='oi', **bar)
Já vimos anteriormente um exemplo do uso de arquivos, aqui nesta seção vamos trabalhar mais a fundo com eles.
Para abrir um arquivo existe a função embutida open()
que recebe, além de outras coisas, o nome do arquivo e modo de abertura. Os modos suportados são:
Character | Meaning |
---|---|
'r' | abrir par leitura (padrão) |
'w' | abrir para escrita, o arquivo é truncado primeiro |
'x' | abrir para criação exclusiva, falhando se o arquivo existe |
'a' | abrir para escrita, anexando o conteúdo para o fim do arquivo caso ele exista |
'b' | modo binário (pode ser usado em conjunto com os de abertura) |
't' | modo texto (padrão) |
'+' | abrir um arquivo do disco para atualização (funciona para escrita e leitura) |
'U' | modo quebra de linhas universal (depreciado) |
Vamos começar abrindo um arquivo de texto para escrita:
In [18]:
arq = open('dados.txt', 'w')
arq
Out[18]:
In [19]:
arq.write('você tem dado em casa?\n')
arq.write('não teve graça... eu sei.')
arq.close()
Agora abra o arquivo dados.txt
e veja seu conteúdo.
Note que após fechar o arquivo não podemos fazer operações nele:
In [20]:
arq.write('esqueci de escrever esta frase')
Agora vamos usar o próprio python para ler o arquivo:
In [21]:
arq = open('dados.txt')
In [26]:
conteudo = arq.read()
conteudo
Out[26]:
Como visto a função file.read()
nos dá o conteúdo de todo o arquivo como uma única string. Mais para frente veremos outros métodos de leitura e escrita.
In [28]:
arq.close() # não podemos esquecer de fechar o arquivo
Agora vamos gerar um arquivo mais complexo com dados mais úteis usando a famigerada biblioteca faker:
In [35]:
from faker import Factory
faker = Factory.create('pt_BR') # cria fábrica de dados falsos em pt-BR
arq = open('dados.txt', 'w')
for _ in range(100):
nome = faker.name()
cargo = faker.job()
empresa = faker.company()
arq.write('"{}","{}","{}"\n'.format(nome, cargo, empresa))
arq.close()
Abra o arquivo dados.txt
e dentro dele haverá 100 linhas. Cada linha contém os dados (nome, cargo e empresa) separados por vírgula.
Esse formato de dados é muito popular e é chamado de CSV (Comma-separated Values ou Valores Separados por Vírgula), muitas empresas usam o CSV para mover dados entre sistemas que trabalham com formatos incompatíveis ou proprietários. Ainda nesta aula veremos mais sobre como trabalhar com esses dados.
Existem algumas maneiras diferentes para ler arquivo. Podemos ler todas as linhas de uma só vez e jogá-la em uma lista (ou iterá-la diretamente) usando file.readlines()
:
In [36]:
arq = open('dados.txt') # abrimos o arquivo no modo padrão (escrita)
linhas = arq.readlines() # o arquivo é todo lido e cada linha vira um elmento na lista linhas
print(type(linhas))
print(linhas)
arq.close()
Podemos iterar a lista e trabalhar com os dados vindo do arquivo:
In [37]:
for linha in linhas[:10]: # pegando só as 10 primeiras por brevidade
print(linha.strip()) # .strip() remove a quebra de linha no final
Como vocês viram anteriormente abrimos o arquivo, mexemos com ele e depois o fechamos. Trabalhar dessa forma geralmente pode levar a erros, pois é comum esquecer de fechar o arquivo e, ter vários arquivos abertos, pode deixar o programa lento/ineficiente. (eu mesmo esqueci de fechar os arquivos nos dois exemplos acima)
Uma maneira melhor de manipular arquivos é usando gerenciadores de contexto se responsabilizam pelo fechamento ou finalização de recursos utilizados e isso pode ser usado para trabalhar com arquivos ou cuidar de transações ao trabalhar com banco de dados, por exemplo.
Vamos mostrar como trabalhar com gerenciadores de contexto lendo o arquivo que criamos anteriormente:
In [38]:
with open('dados.txt') as arq: # abre o arquivo numeros.txt e o coloca na variável arq
for linha in arq.readlines()[-10:]: # pega as 10 últimas linhas por brevidade
print(linha.strip())
O arquivo arq
já foi fechado, podemos verificar isso tentando ler uma linha do arquivo:
In [33]:
arq.readline()
Tambem é possível ler o arquivo linha a linha usando a função file.readline()
:
In [39]:
with open('dados.txt') as arq:
linha = arq.readline()
while linha:
print(linha, end='')
linha = arq.readline()
Não existe nenhum padrão de arquivos CSV. É comum ver arquivos desses formatos separados por outros caracteres que não a vírgula como: ;
-
e .
. Em alguns casos cada coluna pode ser envolta em aspas simples ou aspas duplas. Por conta dessas particularidades o Python criou uma biblioteca csv
que auxilia na manipulação de arquivos CSV.
Para ler arquivos CSV precisamos primeiro importar a biblioteca e depois criamos um leitor CSV com a função csv.reader()
:
In [40]:
import csv
with open("dados.txt") as arq_csv: # abrindo o arquivo
leitor = csv.reader(arq_csv)
for linha in leitor:
print(type(linha), linha)
A função csv.reader()
já retorna cada linha como uma lista Python com as aspas e quebra de linhas removidas.
Podemos deixar a leitura do arquivo ainda melhor usando desempacotamento de sequências:
In [41]:
with open('dados.txt') as arq_csv:
leitor = csv.reader(arq_csv)
for nome, cargo, empresa in leitor:
print('{} trabalha como {} na {}'.format(nome, cargo, empresa))
Agora vamos criar um arquivo CSV em um padrão diferente usando a função csv.writer()
. Nossas colunas serão separadas por espaço em branco e cada coluna será separada por |
ao invés de aspas:
In [44]:
with open('mais-dados.csv', 'w') as arq_csv:
escritor = csv.writer(arq_csv, delimiter=' ', quotechar='|')
for _ in range(20):
dados = faker.name(), faker.job(), faker.company()
escritor.writerow(dados)
Para ler o arquivo é só usar a mesma função csv.reader()
usada anteriormente especificando o padrão:
In [46]:
with open('mais-dados.csv') as arq_csv:
for linha in csv.reader(arq_csv, delimiter=' ', quotechar='|'):
print(linha)
Para mais informações sobre como usar a biblioteca csv
consulte sua documentação oficial
Fim da Aula 05